import pandas as pd
import numpy as np
from datetime import datetime, timedelta
from sklearn.preprocessing import MinMaxScaler
import statsmodels
from statsmodels import tsa
from statsmodels.tsa.stattools import acf
from scipy.signal import butter,sosfiltfilt
from scipy import signal
from scipy import interpolate
from scipy.signal import periodogram,find_peaks
import json
import warnings
warnings.filterwarnings("ignore")

#  ——————————定义温度异常函数—————————
def drop_temp_error(data):
    # 设置参数
    upper_threshold = 1
    lower_threshold = -1
    window_size = 5
    # data['temp'] = data['temp_benchmark'] + data['temp_offset']
    # data = data.loc[data['temp']>30]
    # data.reset_index(drop=True, inplace=True)

    data['rolling_temp'] = data['temp'].rolling(window_size).mean()
    data['diff'] = abs(data['temp'] - data['rolling_temp'])
    data['drop_flag'] = np.where((data['diff'] > upper_threshold) | (data['diff'] < lower_threshold), 1, 0)  # 记录温度是否异常

    if len(data) <= 10:
        data_valid_key = 0
    else:
        mean_th = data['temp'][0:5].mean()
        for i in range(5):
            if abs(data['temp'][i] - mean_th) > 1:
                data['drop_flag'][i] = 1

        data = data[data['drop_flag'] == 0]
        data.reset_index(drop=True, inplace=True)
        if len(data) <= 10:
            data_valid_key = 0
        else:
            data_valid_key = 1

    return data,data_valid_key


# ———————————定义插值函数—————————————
def interpolation(x, y):
    '''

    :param x: 日期
    :param y: 数据
    :return: 插值后的数据
    '''
    insert_x = []    # 存放多项式插值的日期
    mean_x = []      # 存放均值插值的日期

    for i in range(len(x) - 1):
        # 将字符串解析为 datetime 对象
        t1 = datetime.strptime(x[i], "%Y-%m-%d")
        t2 = datetime.strptime(x[i + 1], "%Y-%m-%d")

        # 计算日期差距
        differ = abs((t2 - t1).days)

        if differ < 4:
            # 填充日期至 `insert_x` 列表
            for d in range(1, differ):
                tmp_date = t2 - timedelta(days=d)
                insert_x.append(tmp_date.strftime("%Y-%m-%d"))
        else:
            # 填充日期并分类到 `mean_x` 或 `insert_x`
            for d in range(1, differ):
                tmp_date = t2 - timedelta(days=d)
                num = d  # 当前填充的步数
                diff_day = (tmp_date - datetime.strptime(x[0], "%Y-%m-%d")).days

                if num % 2 and diff_day > 3:
                    mean_x.append(tmp_date.strftime("%Y-%m-%d"))
                else:
                    insert_x.append(tmp_date.strftime("%Y-%m-%d"))


    newxlist = x.to_list() + insert_x + mean_x
    newxlist = sorted(newxlist)    # 得到一个连续的日期列表

    xdict = {}
    resx_dict = {}      # 存放插值后的数据，日期：温度
    x_list = []
    x_i_list = []
    x_m_list = []
    j = 0
    for i in range(len(newxlist)):
        xdict[newxlist[i]] = i + 1
        if newxlist[i] in x.to_list():
            x_list.append(xdict[newxlist[i]])
            resx_dict[newxlist[i]] = y[j]
            j += 1
        elif newxlist[i] in insert_x:
            x_i_list.append(xdict[newxlist[i]])
        elif newxlist[i] in mean_x:
            x_m_list.append(xdict[newxlist[i]])

    # 插值
    Flinear2 = interpolate.interp1d(x_list, y, kind='quadratic')
    x_lists = x_i_list + x_m_list
    for i in sorted(x_lists):
        if i in x_i_list:
            ynew = Flinear2(i)
            ynew = np.array(ynew).tolist()
            ynew = round(ynew, 2)
            k = [k for k, v in xdict.items() if v == i][0]
            resx_dict[k] = ynew
            resx_dict = sorted(resx_dict.items(), key=lambda x: x[0], reverse=False)
            resx_dict = dict(resx_dict)

        else:
            k = [k for k, v in xdict.items() if v == i][0]
            tmp = xdict[k] + 1
            value = round(sum(list(resx_dict.values())[tmp - 6:tmp - 2]) / 4, 2)
            resx_dict[k] = value
            resx_dict = sorted(resx_dict.items(), key=lambda x: x[0], reverse=False)
            resx_dict = dict(resx_dict)


    return resx_dict


# ——————————定义数据预处理函数—————————
# 输入用户数据，
def data_predo(data, name_col):
    '''

    :param data: 数据表
    :param name_col: 做处理的字段
    :return: 插值后的完整数据，以及是否（连续）缺失过多的标签
    '''

    # 排序和删除温度异常
    # data['date_sleep'] = pd.to_datetime(data['date_sleep'])
    data = data.sort_values(by='date_sleep', ascending=True)  # 先按日期排序
    data.reset_index(drop=True, inplace=True)  # 重新索引

    data,error_key = drop_temp_error(data)    # 删除温度异常值
    if error_key == 0:
        del_user = 1
        data['filter'] = 36.2
        return data, del_user

    # 查看缺失情况
    t1 = data['date_sleep'][0].date()
    t2 = data['date_sleep'][len(data) - 1].date()
    days_num = (t2 - t1).days+1

    # 判断是否 连续缺失5天以上或者缺失超过25%的用户id索引
    t_day = 0
    for t in range(1, len(data)):
        t3 = data['date_sleep'][t].date()
        t4 = data['date_sleep'][t-1].date()
        diff_day = (t4 - t3).days + 1
        if diff_day > 5:
            t_day = diff_day

    if len(data) / days_num < 0.15 or t_day != 0:
        del_user = 1
    else:
        del_user = 0

  # 生成一个完整的日期序列，并判断哪些日期是缺失需要插值的，
    date_new = pd.date_range(data['date_sleep'][0], data['date_sleep'][len(data) - 1], freq='D')

    error_version = all(data['error'] == 0)
    if error_version:
        date_error = [0 if i in data['date_sleep'].values else 1 for i in date_new]  # 判断哪天的数据是否是缺失的，1：是；0否
    else:
        error_dict = dict(zip(data['date_sleep'], data['error']))
        date_error = [error_dict[i] if i in error_dict else 1 for i in date_new]
    # 插值
    data['date_sleep'] = data['date_sleep'].astype(str)
    data.drop_duplicates(subset=['date_sleep'],keep='first', inplace=True)
    data = data.reset_index(drop=True)
    resx_dict = interpolation(data['date_sleep'], data[name_col])
    temperature_new = resx_dict.values()

    data_list = []
    for i in range(len(date_new)):
        to_dict = {}
        to_dict['date_sleep'] = str(date_new[i]).split(' ')[0]
        to_dict[name_col] = round(list(temperature_new)[i],2)
        to_dict['error'] = date_error[i]
        data_list.append(to_dict)

    return data_list, del_user

# ——————————定义自相关分类———————————
# def Main_processing(data, name_col ,first_mens_date): # 2.8修改
def Main_processing(data_2, name_col):
    '''

    :param data: 数据表
    :param name_col: 列名，数据表的哪一列做分类
    :param menstrual_start: 用户启用该算法时的日期
    first_mens_date : 用户第一次金标
    :return: 标签，和插值后连续日期的体温数据，A:有规律，C：无规律，F：不满足60天
    '''

    # 定义butter【带通】滤波器函数：
    def butter_bandpass(lowcut, highcut, fs, order=3):
        nyq = 0.5 * fs
        low = lowcut / nyq
        high = highcut / nyq
        sos = butter(order, [low, high], analog=False, btype='band', output='sos')
        return sos

    def butter_bandpass_filter(data, lowcut, highcut, fs, order=3):
        sos = butter_bandpass(lowcut, highcut, fs, order=order)
        y = sosfiltfilt(sos, data)
        return y

    data_list, del_user = data_predo(data_2, name_col)

    # 数据标准化：
    data_z = pd.DataFrame(data_list)
    data_z_scaler = MinMaxScaler().fit_transform(data_z[[name_col]])  #data_z_scaler.reshape(1, -1)
    data_z_scaler = data_z_scaler.reshape(len(data_z))

    sampling_rate = 30  # 采样频率

    # 自相关系数判决区间，由于基数是30
    check_low = 20
    check_high = 40
    check_num = 3  # 选择区间内需要有多少次超过门限

    pro = 0.5  # A类比例门限（即A类数据的低频/（低频+高频）信号的能力比例大于这个门限值，才认为其是A类，否则进行B类判定）
    cutoff_freq_top = 1.8  # A类带通上限，也是B类滤波的滤波器上限
    cutoff_freq_bottom = 0.5  # A类带通下限，也是B类滤波的滤波器下限
    order = 3  # butter滤波器的阶数
    flag = 0

    # 遍历所有用户（自相关处理选择“无偏”）
    if del_user == 1:
        Min_A = 0.3
        Min_B = 0.08
        Min_B2 = 0.1
    else:
        Min_A = 0.2
        Min_B = 0.05
        Min_B2 = 0.08
    tt, conf_int = acf(data_z_scaler, nlags=len(data_z_scaler), alpha=0.05, adjusted=True)  # adjusted 如果为True，则自协方差的分母为n-k，否则为n。

    sorted_list = sorted(tt[check_low:check_high], reverse=True)  # 对列表进行降序排序

    Max = sorted_list[check_num - 1]  # 选择第三大的数据来判断

    if Max > Min_A:
        # 计算功率谱密度
        freqs, psd = signal.welch(data_z_scaler, fs=sampling_rate)  ## average='median'

        # 计算低频和高频范围内的功率
        low_freq_power = np.sum(psd[(freqs >= cutoff_freq_bottom) & (freqs <= cutoff_freq_top)])
        high_freq_power = np.sum(psd[(freqs > cutoff_freq_top)])

        ratio = low_freq_power / (high_freq_power + low_freq_power)

        if ratio > pro:
            user_label= 'A'
        else:
            flag = 1  # 把它丢到B类判断

    if Min_B < Max < Min_A or flag == 1:
        dataB = butter_bandpass_filter(data_z_scaler, 1, 3, sampling_rate, order)  # 利用sosfilt进行带通滤波
        tt, conf_int = statsmodels.tsa.stattools.acf(dataB, nlags=len(dataB), alpha=0.05,
                                                     adjusted=True)  # 滤波后再次acf；
        sorted_list = sorted(tt[check_low:check_high], reverse=True)  # 对列表进行降序排序
        Max2 = sorted_list[check_num - 1]  # 选择第三大的数据来判断

        # 先判断滤波后的自相关系数是否大于原来序列的4倍，如果是，为'C'类，如果不是，进入下一个判断
        # 相关系数大于0.4的，则为’A‘类，如果小于等于0.4的，进入下一个判断
        # 判断周期长度是否在20-40天之间，如果不是，则为‘C’类；如果是，为'A'类
        if Max2 > Min_B2:
            if Max2 / Max > 4:
                user_label = 'C'
            else:
                if Max2 > 0.4:
                    user_label = 'A'
                else:
                    # 周期判断
                    frequencies, Pxx = periodogram(dataB)
                    peaks, _ = find_peaks(Pxx, height=0.15)  # 寻找频率峰值的位置
                    # 找到主要的峰值对应的频率和周期
                    main_frequencies = frequencies[peaks]
                    main_periods = 1 / main_frequencies
                    m1 = main_periods[(main_periods>20) & (main_periods<40)]
                    if len(m1) != 0:
                        user_label = 'A'
                    else:
                        user_label = 'C'

        else:
            user_label = 'C'

    elif Max < Min_B:
        user_label = 'C'

    else:
        user_label = 'C'

    return user_label,data_z, 0

def temp_data_cls(data_old, data_new,data_last, v_app ):
    """
    用户温度数据处理
    包括版本更替导致的数据保存、判断用户佩戴情况
    :param data_old: 2.6以前数据
    :param data_new: 3.6 以后中间参
    :param data_today: 用户最新体温数据
    :param v_app: 用户当前版本号

    """

    main_version = ".".join(v_app.split(".")[:2])  # 取前两部分
    main_version_number = float(main_version.lstrip('v'))
    if main_version_number >= 3.6 and data_new is not None:
        data_new_list = json.loads(data_new)
        temp_date = data_last[0]['date_sleep']
        temp_new = data_last[0]['temp_benchmark'] + data_last[0]['temp_offset']
        temp_now_tuple = [temp_date, round(temp_new,2), 0]

        data_new_list.append(temp_now_tuple)
        data_new_list = pd.DataFrame(data_new_list)
        data_new_list.rename(columns={0:'date_sleep', 1:'temp', 2:'error'}, inplace=True)
        data1 = data_new_list
    else:

        user_data_temp = pd.DataFrame(data_old)

        user_data_temp['temp'] = user_data_temp['temp_benchmark'] + user_data_temp['temp_offset']
        user_data_temp = user_data_temp.sort_values(by=['date_sleep', 'sleep_duration'], ascending=[True, False])
        data1 = (
            user_data_temp.loc[user_data_temp.groupby('date_sleep')['sleep_duration'].idxmax().dropna()]
            .query("temp > 30")
            .dropna(subset=['temp_benchmark'])
            .assign(temp_offset=lambda df: df['temp_offset'].fillna(0))
            .sort_values(by='date_sleep')
        )[['date_sleep', 'temp']]
        data1['error'] = 0
    data1['date_sleep'] = pd.to_datetime(data1['date_sleep'])
    data1['temp'] = round(data1['temp'], 2)

    try:
        if not data1.empty:
            data_2 = data1.loc[data1['date_sleep'] >= (data1['date_sleep'].iloc[-1] - timedelta(days=90))] # 测试时使用，把测试时的最后一天当做是今天, 排卵测试时注释
            temp_type, user_data, user_temp_label = (
                Main_processing(data_2, 'temp') if len(data_2) > 60 else ('F', data1.assign(date_sleep=data1['date_sleep'].astype(str)), 5)
            )

        else:
            data1['date_sleep'] = data1['date_sleep'].apply(lambda x: x.strftime('%Y-%m-%d') if pd.notna(x) else '')
            temp_type, user_data, user_temp_label = 'F', data1, 5

    except Exception as e :
        data1['date_sleep'] = data1['date_sleep'].apply(lambda x: x.strftime('%Y-%m-%d') if pd.notna(x) else '')
        temp_type, user_data, user_temp_label = 'F', data1, 5

    return temp_type, user_data, user_temp_label

